 /*******************************************************************************
  * Copyright (c) 2000, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  * James D Miles (IBM Corp.) - bug 191368, Policy URL doesn't support UTF-8 characters
  *******************************************************************************/
 package org.eclipse.update.internal.operations;

 import java.lang.reflect.*;
 import java.net.*;
 import java.util.ArrayList ;
 import java.util.Vector ;

 import org.eclipse.core.runtime.*;
 import org.eclipse.update.configuration.*;
 import org.eclipse.update.core.*;
 import org.eclipse.update.core.model.*;
 import org.eclipse.update.internal.core.*;
 import org.eclipse.update.internal.core.URLEncoder;
 import org.eclipse.update.internal.search.*;
 import org.eclipse.update.operations.*;
 import org.eclipse.update.search.*;


 /**
  * Helper class for performing update related tasks, queries, etc.
  * All the methods are static and this class should be a singleton.
  */
 public class UpdateUtils {
     public static final String P_UPDATE_POLICY_URL = "updatePolicyURL"; //$NON-NLS-1$

     /**
      * Private constructor
      */
     private UpdateUtils() {
     }
     

     public static String getPluginId() {
         return UpdateCore.getPlugin().getBundle().getSymbolicName();
     }


     public static void logException(Throwable e) {
         
         if (e instanceof InvocationTargetException) {
             e = ((InvocationTargetException) e).getTargetException();
         }

         IStatus status = null;
         if (e instanceof CoreException) {
             status = ((CoreException) e).getStatus();
         } else {
             String message = e.getMessage();
             if (message == null)
                 message = e.toString();
             status = new Status(IStatus.ERROR, getPluginId(), IStatus.OK, message, e);
         }
         log(status);
     }

     public static void log(IStatus status) {
         if (status.getSeverity() != IStatus.INFO) {
             UpdateCore.getPlugin().getLog().log(status);
         }
     }

     public static IFeature[] searchSite(String featureId, IConfiguredSite site, boolean onlyConfigured) throws CoreException {
         IFeatureReference[] references = null;

         if (onlyConfigured)
             references = site.getConfiguredFeatures();
         else
             references = site.getSite().getFeatureReferences();
         Vector result = new Vector ();

         for (int i = 0; i < references.length; i++) {
             IFeature feature = references[i].getFeature(null);
             String id = feature.getVersionedIdentifier().getIdentifier();
             if (featureId.equals(id)) {
                 result.add(feature);
             }
         }
         return (IFeature[]) result.toArray(new IFeature[result.size()]);
     }

     public static IFeature[] getInstalledFeatures(IFeature feature) {
         return getInstalledFeatures(feature, true);
     }

     /**
      *
      * @param feature
      * @param onlyConfigured
      * @return IFeature[] with features matching feature ID
      */
     public static IFeature[] getInstalledFeatures(IFeature feature, boolean onlyConfigured) {
         return getInstalledFeatures(feature.getVersionedIdentifier(), onlyConfigured);
     }
     /**
      * @param vid
      * @param onlyConfigured
      * @return IFeature[] with features matching feature ID
      */
     public static IFeature[] getInstalledFeatures(VersionedIdentifier vid, boolean onlyConfigured) {
         Vector features = new Vector ();
         try {
             ILocalSite localSite = SiteManager.getLocalSite();
             IInstallConfiguration config = localSite.getCurrentConfiguration();
             IConfiguredSite[] isites = config.getConfiguredSites();
             String id = vid.getIdentifier();

             for (int i = 0; i < isites.length; i++) {
                 IConfiguredSite isite = isites[i];
                 IFeature[] result = UpdateUtils.searchSite(id, isite, onlyConfigured);
                 for (int j = 0; j < result.length; j++) {
                     IFeature installedFeature = result[j];
                     features.add(installedFeature);
                 }
             }
         } catch (CoreException e) {
             UpdateUtils.logException(e);
         }
         return (IFeature[]) features.toArray(new IFeature[features.size()]);
     }
     
     /**
      * @param patch
      * @return IFeature or null
      */
     public static IFeature getPatchedFeature(IFeature patch) {
         IImport[] imports = patch.getImports();
         for (int i = 0; i < imports.length; i++) {
             IImport iimport = imports[i];
             if (iimport.isPatch()) {
                 VersionedIdentifier patchedVid = iimport
                         .getVersionedIdentifier();
                 // features with matching id
 IFeature[] features = getInstalledFeatures(patchedVid, false);
                 for (int f = 0; f < features.length; f++) {
                     // check if version match
 if (patchedVid.equals(features[f].getVersionedIdentifier())) {
                         return features[f];
                     }
                 }
             }
         }
         return null;
     }

     public static boolean isPatch(IFeature candidate) {
         IImport[] imports = candidate.getImports();

         for (int i = 0; i < imports.length; i++) {
             IImport iimport = imports[i];
             if (iimport.isPatch()) return true;
         }
         return false;
     }

     public static boolean isPatch(IFeature target, IFeature candidate) {
         VersionedIdentifier vid = target.getVersionedIdentifier();
         IImport[] imports = candidate.getImports();

         for (int i = 0; i < imports.length; i++) {
             IImport iimport = imports[i];
             if (iimport.isPatch()) {
                 VersionedIdentifier ivid = iimport.getVersionedIdentifier();
                 if (vid.equals(ivid)) {
                     // Bingo.
 return true;
                 }
             }
         }
         return false;
     }

     public static IInstallConfiguration getBackupConfigurationFor(IFeature feature) {
         VersionedIdentifier vid = feature.getVersionedIdentifier();
         String key = "@" + vid.getIdentifier() + "_" + vid.getVersion(); //$NON-NLS-1$ //$NON-NLS-2$
 try {
             ILocalSite lsite = SiteManager.getLocalSite();
             IInstallConfiguration[] configs = lsite.getPreservedConfigurations();
             for (int i = 0; i < configs.length; i++) {
                 IInstallConfiguration config = configs[i];
                 if (config.getLabel().startsWith(key))
                     return config;
             }
         } catch (CoreException e) {
         }
         return null;
     }
     
     
     public static boolean hasLicense(IFeature feature) {
         IURLEntry info = feature.getLicense();
         if (info == null)
             return false;
         String licenseTxt = info.getAnnotation();
         if (licenseTxt == null)
             return false;
         return licenseTxt.trim().length() > 0;
     }
     public static boolean hasOptionalFeatures(IFeatureReference fref) {
         try {
             return hasOptionalFeatures(fref.getFeature(null));
         } catch (CoreException e) {
             return false;
         }
     }
     public static boolean hasOptionalFeatures(IFeature feature) {
         try {
             IIncludedFeatureReference[] irefs = feature.getIncludedFeatureReferences();
             for (int i = 0; i < irefs.length; i++) {
                 IIncludedFeatureReference iref = irefs[i];
                 if (iref.isOptional())
                     return true;
                 // see if it has optional children
 IFeature child = getIncludedFeature( feature, iref);
                 if (hasOptionalFeatures(child))
                     return true;
             }
         } catch (CoreException e) {
         }
         return false;
     }

     public static IFeature getLocalFeature(
         IConfiguredSite csite,
         IFeature feature)
         throws CoreException {
         IFeatureReference[] refs = csite.getConfiguredFeatures();
         for (int i = 0; i < refs.length; i++) {
             IFeatureReference ref = refs[i];
             VersionedIdentifier refVid = ref.getVersionedIdentifier();
             if (feature.getVersionedIdentifier().equals(refVid))
                 return ref.getFeature(null);
         }
         return null;
     }
     
     public static IConfiguredSite getConfigSite(
         IFeature feature,
         IInstallConfiguration config)
         throws CoreException {
         IConfiguredSite[] configSites = config.getConfiguredSites();
         for (int i = 0; i < configSites.length; i++) {
             IConfiguredSite site = configSites[i];
             if (site.getSite().equals(feature.getSite())) {
                 return site;
             }
         }
         return null;
     }
     
     public static IConfiguredSite getDefaultTargetSite(
         IInstallConfiguration config,
         IInstallFeatureOperation pendingChange) {
         return getDefaultTargetSite(config, pendingChange, true);
     }

     public static IConfiguredSite getDefaultTargetSite(
         IInstallConfiguration config,
         IInstallFeatureOperation pendingChange,
         boolean checkAffinityFeature) {
         IFeature oldFeature = pendingChange.getOldFeature();
         IFeature newFeature = pendingChange.getFeature();
         if (oldFeature != null) {
             // We should install into the same site as
 // the old feature
 try {
                 return getConfigSite(oldFeature, config);
             } catch (CoreException e) {
                 logException(e);
                 return null;
             }
         }

         // This is a new install. Check if there is
 // a disabled feature with the same ID
 String newFeatureID =
             newFeature.getVersionedIdentifier().getIdentifier();
         IConfiguredSite sameSite = getSiteWithFeature(config, newFeatureID);
         if (sameSite != null) {
             return sameSite;
         }

         if (checkAffinityFeature) {
             return getAffinitySite(config, newFeature);
         }
         return null;
     }
     
     public static IConfiguredSite getAffinitySite(
         IInstallConfiguration config,
         IFeature newFeature) {
         // check if the affinity feature is installed
 String affinityID = newFeature.getAffinityFeature();
         if (affinityID != null) {
             IConfiguredSite affinitySite =
                 getSiteWithFeature(config, affinityID);
             if (affinitySite != null)
                 return affinitySite;
         } else {
             // if this is a patch, collocate with the feature
 IFeature patchedFeature = getPatchedFeature(newFeature);
             if (patchedFeature != null)
                 return getSiteWithFeature(config, patchedFeature.getVersionedIdentifier().getIdentifier());
         }
         return null;
     }

     public static IConfiguredSite getSiteWithFeature(
         IInstallConfiguration config,
         String featureID) {
         if (featureID == null)
             return null;
         IConfiguredSite[] sites = config.getConfiguredSites();
         for (int i = 0; i < sites.length; i++) {
             IConfiguredSite site = sites[i];
             IFeatureReference[] refs = site.getFeatureReferences();
             for (int j = 0; j < refs.length; j++) {
                 IFeatureReference ref = refs[j];
                 try {
                     IFeature feature = ref.getFeature(null);
                     if (featureID
                         .equals(
                             feature
                                 .getVersionedIdentifier()
                                 .getIdentifier())) {
                         // found it
 return site;
                     }
                 } catch (CoreException e) {
                     logException(e);
                 }
             }
         }
         return null;
     }
     
     public static void collectOldFeatures(
         IFeature feature,
         IConfiguredSite targetSite,
         ArrayList result)
         throws CoreException {
         IIncludedFeatureReference[] included =
             feature.getIncludedFeatureReferences();
         for (int i = 0; i < included.length; i++) {
             IIncludedFeatureReference iref = included[i];

             IFeature ifeature;

             try {
                 ifeature = iref.getFeature(null);
             } catch (CoreException e) {
                 if (iref.isOptional())
                     continue;
                 throw e;
             }
             // find other features and unconfigure
 String id = iref.getVersionedIdentifier().getIdentifier();
             IFeature[] sameIds = UpdateUtils.searchSite(id, targetSite, true);
             for (int j = 0; j < sameIds.length; j++) {
                 IFeature sameId = sameIds[j];
                 // Ignore self.
 if (sameId.equals(ifeature))
                     continue;
                 result.add(sameId);
             }
             collectOldFeatures(ifeature, targetSite, result);
         }
     }

 //
 // public static IInstallConfiguration createInstallConfiguration() throws CoreException{
 // try {
 // ILocalSite localSite = SiteManager.getLocalSite();
 // IInstallConfiguration config =
 // localSite.cloneCurrentConfiguration();
 // config.setLabel(Utilities.format(config.getCreationDate()));
 // return config;
 // } catch (CoreException e) {
 // // Let callers handle logging
 // //logException(e);
 // throw e;
 // }
 // }

     public static UpdateSearchRequest createNewUpdatesRequest(IFeature [] features) {
         return createNewUpdatesRequest(features, true);
     }
     
     public static UpdateSearchRequest createNewUpdatesRequest(IFeature [] features, boolean automatic) {
         UpdateSearchScope scope = new UpdateSearchScope();
         scope.setUpdateMapURL(UpdateUtils.getUpdateMapURL());
         UpdatesSearchCategory category = new UpdatesSearchCategory(automatic);
         if (features!=null)
             category.setFeatures(features);
         UpdateSearchRequest searchRequest = new UpdateSearchRequest(category, scope);
         searchRequest.addFilter(new EnvironmentFilter());
         searchRequest.addFilter(new BackLevelFilter());
         return searchRequest;
     }

     public static boolean isNestedChild(IInstallConfiguration config, IFeature feature) {
         IConfiguredSite[] csites = config.getConfiguredSites();
         try {
             for (int i = 0; csites != null && i < csites.length; i++) {
                 IFeatureReference[] refs = csites[i].getConfiguredFeatures();
                 for (int j = 0; refs != null && j < refs.length; j++) {
                     IFeature parent = refs[j].getFeature(null);
                     IFeatureReference[] children =
                         parent.getIncludedFeatureReferences();
                     for (int k = 0;
                         children != null && k < children.length;
                         k++) {
                         IFeature child = children[k].getFeature(null);
                         if (feature.equals(child))
                             return true;
                     }
                 }
             }
         } catch (CoreException e) {
             // will return false
 }
         return false;
     }
     
     
     public static boolean hasObsoletePatches(IFeature feature) {
         // Check all the included features that
 // are unconfigured, and see if their patch
 // references are better than the original.
 try {
             IFeatureReference[] irefs = feature.getIncludedFeatureReferences();
             for (int i = 0; i < irefs.length; i++) {
                 IFeatureReference iref = irefs[i];
                 IFeature ifeature = iref.getFeature(null);
                 IConfiguredSite csite = ifeature.getSite().getCurrentConfiguredSite();
                 if (!csite.isConfigured(ifeature)) {
                     if (!isPatchHappy(ifeature))
                         return false;
                 }
             }
         } catch (CoreException e) {
             return false;
         }
         // All checks went well
 return true;
     }
     
     public static boolean isPatchHappy(IFeature feature) throws CoreException {
         // If this is a patch and it includes
 // another patch and the included patch
 // is disabled but the feature it was declared
 // to patch is now newer (and is presumed to
 // contain the now disabled patch), and
 // the newer patched feature is enabled,
 // a 'leap of faith' assumption can be
 // made:

         // Although the included patch is disabled,
 // the feature it was designed to patch
 // is now newer and most likely contains
 // the equivalent fix and more. Consequently,
 // we can claim that the status and the error
 // icon overlay are misleading because
 // all the right plug-ins are configured.
 IImport[] imports = feature.getImports();
         IImport patchReference = null;
         for (int i = 0; i < imports.length; i++) {
             IImport iimport = imports[i];
             if (iimport.isPatch()) {
                 patchReference = iimport;
                 break;
             }
         }
         if (patchReference == null)
             return false;
         VersionedIdentifier refVid = patchReference.getVersionedIdentifier();

         // Find the patched feature and
 IConfiguredSite csite = feature.getSite().getCurrentConfiguredSite();
         if (csite == null)
             return false;

         IFeatureReference[] crefs = csite.getConfiguredFeatures();
         for (int i = 0; i < crefs.length; i++) {
             IFeatureReference cref = crefs[i];
             VersionedIdentifier cvid = cref.getVersionedIdentifier();
             if (cvid.getIdentifier().equals(refVid.getIdentifier())) {
                 // This is the one.
 if (cvid.getVersion().isGreaterThan(refVid.getVersion())) {
                     // Bingo: we found the referenced feature
 // and its version is greater -
 // we can assume that it contains better code
 // than the patch that referenced the
 // older version.
 return true;
                 }
             }
         }
         return false;
     }

     public static URL getUpdateMapURL() {
         Preferences pref = UpdateCore.getPlugin().getPluginPreferences();
         String mapFile = pref.getString(UpdateUtils.P_UPDATE_POLICY_URL);
         if (mapFile!=null && mapFile.length()>0) {
             try {
                 URL url = new URL(mapFile);
                 URL resolvedURL = URLEncoder.encode(url);
                 return resolvedURL;
             }
             catch (MalformedURLException e) {
                 UpdateUtils.logException(e);
             }
         }
         return null;
     }
     
     /*
      * Load the update map using the map URL found in the scope.
      */
     public static IStatus loadUpdatePolicy(UpdatePolicy map, URL url, IProgressMonitor monitor) throws CoreException {
         monitor.subTask(Messages.UpdateSearchRequest_loadingPolicy);
         try {
             map.load(url, monitor);
             monitor.worked(1);
         }
         catch (CoreException e) {
             IStatus status = e.getStatus();
             if (status == null
                 || status.getCode() != ISite.SITE_ACCESS_EXCEPTION)
                 throw e;
             monitor.worked(1);
             return status;
         }
         return null;
     }

     public static void downloadFeatureContent(
         IConfiguredSite targetSite,
         IFeature feature,
         IFeatureReference[] optionalChildren, // null when feature has no optional features
 IProgressMonitor progress)
         throws InstallAbortedException, CoreException {
         
         //DEBUG
 if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_INSTALL) {
             UpdateCore.debug(
                 "Downloading...:" + feature.getURL().toExternalForm()); //$NON-NLS-1$
 }

         // Get source feature provider and verifier.
 // Initialize target variables.
 final IFeatureContentProvider provider =
             feature.getFeatureContentProvider();
         IPluginEntry[] targetSitePluginEntries = null;

         // determine list of plugins to install
 // find the intersection between the plugin entries already contained
 // on the target site, and plugin entries packaged in source feature
 IPluginEntry[] sourceFeaturePluginEntries = feature.getPluginEntries();

         boolean featureAlreadyInstalled = false;
         if (targetSite == null)
             targetSite = getSiteWithFeature(SiteManager.getLocalSite()
                     .getCurrentConfiguration(), ((Feature) feature)
                     .getFeatureIdentifier());
         if (targetSite == null) {
             if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_INSTALL) {
                 UpdateCore.debug("The site to install in is null"); //$NON-NLS-1$
 }

             targetSitePluginEntries = new IPluginEntry[0];
         } else {
             targetSitePluginEntries = targetSite.getSite().getPluginEntries();
             featureAlreadyInstalled = UpdateUtils.getLocalFeature(targetSite,feature) != null;
         }
         IPluginEntry[] pluginsToInstall =
             UpdateManagerUtils.diff(
                 sourceFeaturePluginEntries,
                 targetSitePluginEntries);
         INonPluginEntry[] nonPluginsToInstall = feature.getNonPluginEntries();
         
         IFeatureReference[] children = feature.getIncludedFeatureReferences();
         if (optionalChildren != null) {
             children =
                 UpdateManagerUtils.optionalChildrenToInstall(
                     children,
                     optionalChildren);
         }

         // make sure we have an InstallMonitor
 InstallMonitor monitor;
         if (progress == null)
             monitor = new InstallMonitor(new NullProgressMonitor());
         else if (progress instanceof InstallMonitor)
             monitor = (InstallMonitor) progress;
         else
             monitor = new InstallMonitor(progress);

         try {
             // determine number of monitor tasks
 // 1 task1 for the feature jar (download)
 // + n tasks for plugin entries (download for each)
 // + m tasks per non-plugin data entry (download for each)
 // TODO custom install handler + 1 task for custom non-plugin entry handling (1 for all combined)
 // + 3*x tasks for children features (3 subtasks per install)
 int taskCount =
                     1
                     + pluginsToInstall.length
                     + nonPluginsToInstall.length
 // + 1
 + 3 * children.length;
             monitor.beginTask("", taskCount); //$NON-NLS-1$

             // Download feature archive(s)
 provider.getFeatureEntryArchiveReferences(monitor);
             monitorWork(monitor,1);
             
             // Download plugin archives
 for (int i = 0; i < pluginsToInstall.length; i++) {
                 provider.getPluginEntryArchiveReferences(pluginsToInstall[i], monitor);
                 monitorWork(monitor,1);
             }
             
             

             // Download non-plugin archives. Verification handled by optional install handler
 // Note: do not download non-plugin archives for installed features
 if (nonPluginsToInstall.length > 0) {
                 // Setup optional install handler
 InstallHandlerProxy handler = null;
                 if (feature.getInstallHandlerEntry()!=null)
                     handler = new InstallHandlerProxy(
                         IInstallHandler.HANDLER_ACTION_INSTALL,
                         feature,
                         feature.getInstallHandlerEntry(),
                         monitor);
                 
                 if (!featureAlreadyInstalled)
                     for (int i = 0; i < nonPluginsToInstall.length; i++) {
                         if (handler==null || handler.acceptNonPluginData(nonPluginsToInstall[i]))
                             provider.getNonPluginEntryArchiveReferences(
                                 nonPluginsToInstall[i], monitor);
                         monitorWork(monitor, 1);
                     }
                 else
                     monitorWork(monitor, nonPluginsToInstall.length);
             }
            
             // Download child features
 for (int i = 0; i < children.length; i++) {
                 IFeature childFeature = null;
                 try {
                     childFeature = children[i].getFeature(null);
                 } catch (CoreException e) {
                     UpdateCore.warn(null, e);
                 }
                 if (childFeature != null) {
                     SubProgressMonitor subMonitor = new SubProgressMonitor(monitor, 3);
                     downloadFeatureContent(targetSite, childFeature, optionalChildren, subMonitor);
                 }
             }
         } finally {
             if (monitor != null)
                 monitor.done();
         }
     }
     
     private static void monitorWork(IProgressMonitor monitor, int tick)
     throws CoreException {
     if (monitor != null) {
         monitor.worked(tick);
         if (monitor.isCanceled()) {
             String msg = "download cancelled";//Policy.bind("Feature.InstallationCancelled"); //$NON-NLS-1$
 throw new InstallAbortedException(msg, null);
         }
     }
 }
     public static IFeature getIncludedFeature(IFeature feature, IFeatureReference iref) throws CoreException {
         IFeature ifeature = null;
         if (feature.getSite() instanceof ExtendedSite) {
             ifeature = ((ExtendedSite)feature.getSite()).getLiteFeature(iref.getVersionedIdentifier());
         }
         if (ifeature == null) {
             ifeature = iref.getFeature(new NullProgressMonitor());
         }
         return ifeature;
     }
 }

